// 获取其最近的滚动容器（包括body）
function getScrollContainer(element) {
  if ((!element) instanceof HTMLElement) return window;
  let container = element;
  while (
    container === undefined ||
    (container !== window && container.nodeType !== Node.DOCUMENT_NODE)
  ) {
    const overflowY = window.getComputedStyle
      ? window.getComputedStyle(container).overflowY
      : container.currentStyle.overflowY;
    if (overflowY == 'scroll' || overflowY == 'auto') {
      break;
    } else {
      container = container.parentNode;
    }
  }

  return container.nodeName == 'HTML' ||
    container.nodeType == Node.DOCUMENT_NODE
    ? window
    : container;
}

// 获取监听节点的父级可竖直滚动元素，如果获取不到，就返回window
function getRootNode(nodeList) {
  const firstDomItem = nodeList.find(i => i instanceof HTMLElement);

  return firstDomItem ? getScrollContainer(firstDomItem) : window;
}

// 获取元素相对浏览器的高度
function getElementTop(element) {
  if (element == window) return 0;
  let actualTop = element?.offsetTop;
  if (typeof actualTop === 'undefined') return false;

  let current = element?.offsetParent;

  while (current !== null) {
    actualTop += current.offsetTop;
    current = current.offsetParent;
  }

  return actualTop;
}

// 滚动到对应节点那里
function scrollToNode(rootNode, node, scrollOffset) {
  const pageTop = getElementTop(rootNode);
  const titleTop = getElementTop(node);
  if (pageTop === false || titleTop === false) return;

  // 滑动到节点处
  rootNode.scrollTo({
    top: titleTop - pageTop - scrollOffset,
    behavior: 'smooth',
  });
}

// 监听节点元素在滚动容器里是否出现或遮挡
// 入参：(滚动容器节点， 监听的节点列表， 回调绑定更新的index的ref对象)
function observeNodes(rootNode, nodeList, scrollOffset, changeCallFn) {
  if (!rootNode || nodeList.length == 0) return;
  let intersectList = new Set();

  const io = new IntersectionObserver(
    entrys => {
      entrys.forEach(ele => {
        const { isIntersecting, target } = ele;
        const index = nodeList.findIndex(v => v == target);
        if (isIntersecting) {
          intersectList.add(index);
        } else {
          intersectList.delete(index);
        }

        // 回调
        const list = Array.from(intersectList).map(n => parseInt(n));
        changeCallFn(list);

        // console.log('====== title```', index, scrollDirection, isIntersecting, intersectList, currentAnchorIndex, ele)
      });
    },
    {
      // 表示重叠面积占被观察者的比例，从 0 - 1 取值，
      // 1 表示完全被包含
      threshold: [0, 0.5, 1],
      // 滚动的容器，必须是目标元素的父级元素
      root: rootNode == window ? null : rootNode,
      //即滚动到距离底部多少px时开始算交互区
      rootMargin: `${scrollOffset}px 0px ${scrollOffset}px 0px`,
    },
  );

  // 注册要监听的元素
  for (let index = 0; index < nodeList.length; index++) {
    const ele = nodeList[index]; // 约定的ref格式 title0 title1
    if (ele && ele instanceof HTMLElement) {
      io.observe(ele);
    } else {
      console.warn(`锚点第${index}个元素不是一个dom节点：`, ele);
    }
  }

  return io;
}

// 监听断联
function disconnectIo(io) {
  // 之前的监听要断掉
  if (io && typeof io.disconnect === 'function') {
    io.disconnect();
    return true;
  }
  return false;
}

// 锚点实体类
export default class AnchorEntities {
  // 监听器，保证只有一个
  io = null;
  // 向下滚动bottom 向上滚动top
  scrollDirection = 'bottom';
  // 滚动偏移
  scrollOffset = 0;
  // 节点列表
  nodeList = [];
  // 滚动的根dom节点
  rootNode = null;

  // 初始化注册监听
  init(nodeList, scrollOffset, changeCallFn) {
    if (!Array.isArray(nodeList) || nodeList.length === 0) {
      this.reset();
      return;
    }

    const rootNode = getRootNode(nodeList);
    if (!(rootNode == window || rootNode instanceof HTMLElement)) {
      this.reset();
      return;
    }

    this.disconnectIo();
    this.nodeList = nodeList;
    this.rootNode = rootNode;
    this.scrollOffset = scrollOffset;
    // 跟踪跟节点滚动方向
    this.traceScrollDirection();
    // 监听节点
    this.io = observeNodes(rootNode, nodeList, 0, intersectList =>
      changeCallFn(intersectList, this.scrollDirection),
    );
  }

  // 滚到某个节点
  scrollToNode(index) {
    const node = this.nodeList[index];
    scrollToNode(this.rootNode, node, this.scrollOffset);
  }

  // 断掉监听
  disconnectIo() {
    disconnectIo(this.io);
  }

  // 重置监听
  reset() {
    this.nodeList = [];
    this.rootNode = null;
    this.disconnectIo();
  }

  traceScrollDirection() {
    let lastOffsetTop = 0;

    const { rootNode } = this;
    // 根节点滚动的其他回调事件
    rootNode.addEventListener('scroll', e => {
      // 判断滚动方向
      const isWindow = rootNode == window;
      const offsetTop = isWindow
        ? window.pageYOffset ?? document.documentElement.scrollTop
        : rootNode.scrollTop;
      if (lastOffsetTop < offsetTop) {
        this.scrollDirection = 'bottom';
      } else {
        this.scrollDirection = 'top';
      }
      lastOffsetTop = offsetTop;
    });
  }
}
